using DWORD = System.UInt32;
using HANDLE = System.IntPtr;
using PHANDLE = System.IntPtr;
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
using System.Security.AccessControl;

namespace NTFSBypassModule
{
    class BackupAPIFile
    {
        [DllImport("kernel32")]
        private static extern HANDLE CreateFile(string Name, FileAccessAPI Access, FileShare Share, int Security, FileMode Creation, FileFlags Flags, int Template);
        private static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        [Flags()]
        private enum FileAccessAPI : uint
        {
            GENERIC_READ = 0x80000000,
            READ_CONTROL = 0x00020000,
            GENERIC_WRITE = 0x40000000,
            WRITE_DAC = 0x40000,
            WRITE_OWNER = 0x80000
        }

        [Flags()]
        private enum FileFlags : uint
        {
            FILE_FLAG_WRITE_THROUGH = 0x80000000,
            FILE_FLAG_OVERLAPPED = 0x40000000,
            FILE_FLAG_NO_BUFFERING = 0x20000000,
            FILE_FLAG_RANDOM_ACCESS = 0x10000000,
            FILE_FLAG_SEQUENTIAL_SCAN = 0x8000000,
            FILE_FLAG_DELETE_ON_CLOSE = 0x4000000,
            FILE_FLAG_BACKUP_SEMANTICS = 0x2000000,
            FILE_FLAG_POSIX_SEMANTICS = 0x1000000,
            FILE_FLAG_OPEN_REPARSE_POINT = 0x200000,
            FILE_FLAG_OPEN_NO_RECALL = 0x100000,
            FILE_ATTRIBUTE_NORMAL = 0x00000080
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct LUID
        {
            public int LowPart;
            public int HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct TOKEN_PRIVILEGES
        {
            public uint PriviledgeCount;
            public LUID Luid;
            public uint Attributes;
        }

        [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
        private static extern int LookupPrivilegeValue(string lpsystemname,
            string lpname,
            [MarshalAs(UnmanagedType.Struct)] 
            ref LUID lpLuid
        );

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern System.Boolean OpenProcessToken(
            int ProcessHandle,
            uint DesiredAccess,
            ref int Tokenhandle
        );

        [DllImport("KERNEL32.DLL")]
        private static extern int CloseHandle(int handle);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern int AdjustTokenPrivileges(
            int TokenHandle,
            int DisableAllPrivileges,
            [MarshalAs(UnmanagedType.Struct)] ref TOKEN_PRIVILEGES NewState,
            int BufferLength,
            int PreviousState,
            int ReturnLength
        );

        private const string SE_BACKUP_NAME = "SeBackupPrivilege";

        private const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
        private const int TOKEN_QUERY = 0x00000008;
        private const int SE_PRIVILEGE_ENABLED = 0x00000002;
        private const int ERROR_SUCCESS = 0;


        private string _path;
        private bool _exists;
        private bool _isFile;
        private bool _isDirectory;
        private DateTime _creationTime;
        private DateTime _lastAccessTime;
        private DateTime _lastWriteTime;
        private long _fileSize;

        private static bool _backupPrivilegeEnabled;


        public BackupAPIFile(string path)
        {
            // Temporarily revert the thread back to the processes idenity
            WindowsImpersonationContext wic = WindowsIdentity.Impersonate(IntPtr.Zero);

            EnableBackupPrivilege();

            try
            {
                if (File.Exists(path))
                {
                    _isFile = true;
                    _exists = true;
                    _creationTime = File.GetCreationTime(path);
                    _lastAccessTime = File.GetLastAccessTime(path);
                    _lastWriteTime = File.GetLastWriteTime(path);
                    _fileSize = new FileInfo(path).Length;
                }
                else if (Directory.Exists(path))
                {
                    _exists = true;
                    _isDirectory = true;
                    _creationTime = Directory.GetCreationTime(path);
                    _lastAccessTime = Directory.GetLastAccessTime(path);
                    _lastWriteTime = Directory.GetLastWriteTime(path);
                }

                _path = path;
            }
            finally
            {
                wic.Undo();
            }
        }

        public bool Exists
        {
            get
            {
                return (_exists);
            }
        }

        public bool isFile
        {
            get
            {
                return (_isFile);
            }
        }

        public bool isDirectory
        {
            get
            {
                return (_isDirectory);
            }

        }

        public string[] GetFiles()
        {
            if (_isDirectory)
            {
                EnableBackupPrivilege();

                // Temporarily revert the thread back to the processes idenity
                WindowsImpersonationContext wic = WindowsIdentity.Impersonate(IntPtr.Zero);

                try
                {
                    return(Directory.GetFiles(_path));
                }
                finally
                {
                    wic.Undo();
                }
            }
            else
                return(null);
        }

        public string[] GetDirectories()
        {
            if (_isDirectory)
            {
                EnableBackupPrivilege();

                // Temporarily revert the thread back to the processes idenity
                WindowsImpersonationContext wic = WindowsIdentity.Impersonate(IntPtr.Zero);

                try
                {
                    return (Directory.GetDirectories(_path));
                }
                finally
                {
                    wic.Undo();
                }

            }
            else
                return (null);
        }


        public DateTime GetLastWriteTime
        {
            get
            {
                return (_lastWriteTime);
            }
        }

        public DateTime GetCreationTime
        {
            get
            {
                return (_creationTime);
            }
        }

        public DateTime GetLastAccessTime
        {
            get
            {
                return (_lastAccessTime);
            }
        }

        public long FileSize
        {
            get
            {
                return (_fileSize);
            }
        }

        public FileStream Open()
        {
            EnableBackupPrivilege();

            // Temporarily revert the thread back to the processes idenity
            WindowsImpersonationContext wic = WindowsIdentity.Impersonate(IntPtr.Zero);

            try
            {
                // Get a handle to the file with backup semantics
                HANDLE fileHandle = CreateFile(_path, FileAccessAPI.GENERIC_READ, FileShare.Read, 0, FileMode.Open, FileFlags.FILE_FLAG_BACKUP_SEMANTICS, 0);

                // If we can't open the file for some reason throw an IO exception
                if (fileHandle == INVALID_HANDLE_VALUE)
                    throw new IOException("CreateFile failed with error code " + Marshal.GetLastWin32Error() + " for " + _path);

                // Convert the file handle to a safe file handle
                SafeFileHandle safeFileHandle = new SafeFileHandle(fileHandle, true);

                // Create a new file stream from the safe file handle
                FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Read);

                // return the new file stream
                return (fileStream);
            }
            finally
            {
                wic.Undo();
            }

        }

        public static bool EnableBackupPrivilege()
        {
            if (!_backupPrivilegeEnabled)
            {
                if (EnablePrivilege(SE_BACKUP_NAME))
                {
                    _backupPrivilegeEnabled = true;
                    return true;
                }
            }

            return (_backupPrivilegeEnabled);
        }

        private static bool EnablePrivilege(string PrivilegeName)
        {
            // TODO: It would be nice if we could enable this right on the current thread, however it seems to work only on the processes...

            TOKEN_PRIVILEGES tokenPrivilege = new TOKEN_PRIVILEGES();
            // TOKEN_PRIVILEGES tokenPrivilegeOld = new TOKEN_PRIVILEGES();
            LUID tokenId = new LUID();

            int TokenHandle = 0;

            try
            {
                // Lookup the value of the privilege
                if (LookupPrivilegeValue(null, PrivilegeName, ref tokenId) == 0)
                    throw new UnauthorizedAccessException();

                // Get the current process handle
                int processHandle = Process.GetCurrentProcess().Handle.ToInt32();

                // Reads the proecss token of the current process
                if (!OpenProcessToken(processHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref TokenHandle))
                    throw new UnauthorizedAccessException();

                // Build the new privilege token
                tokenPrivilege.PriviledgeCount = 1;
                tokenPrivilege.Attributes = SE_PRIVILEGE_ENABLED;
                tokenPrivilege.Luid = tokenId;

                // Adjust the current process token to include the new privilege token
                int tokenPrivilegeLength = Marshal.SizeOf(tokenPrivilege);
                if (AdjustTokenPrivileges(TokenHandle, 0, ref tokenPrivilege, tokenPrivilegeLength, 0, 0) != 1 || Marshal.GetLastWin32Error() != ERROR_SUCCESS)
                    throw new PrivilegeNotHeldException(PrivilegeName);
            }
            catch (Exception e)
            {
                throw (e);
            }
            finally
            {
                // alwase attempt to close the current token handle
                CloseHandle(TokenHandle);
            }

            return (true);
        }
    }
}
